🧪 QA Mobile · Version 1.0

Task Mobile v34.0 · Test Scenarios
+ Playwright Mobile Automation Guide

Comprehensive mobile test documentation for ConstructQ Task Mobile v34.0 · 16 test areas · 82 test cases · 4 roles · 3 bottom-sheet pickers · Checklist wizard with NCR loop · Playwright Pixel 5 / iPhone 14 emulation
📅 วันที่: 13 ก.ค. 2569 (2026-07-13) 👤 Owner: วุธ (PM/SA · QA Lead) 🎯 Audience: QA Mobile · Dev · Automation 📄 Source: ConstructQ_TaskMobile_v34.0.html
16
Test Areas
82
Test Cases
4
Roles Supported
3
Bottom-Sheet Pickers

01บทนำ · About This Guide

Test scope · deliverables · mobile-specific coverage

เอกสารนี้เป็น Mobile Test Documentation สำหรับหน้าจอ Task Mobile v34.0 ครอบคลุม:

  • Test Scenarios · 16 areas — 4 roles · Create Task · 3 pickers · Checklist wizard · NCR flow
  • Test Cases · 82 cases — Positive / Negative / Boundary / UI / Mobile / Integration / A11y
  • Playwright Mobile Guide — Pixel 5 + iPhone 14 emulation · POM · 5 sample test scripts
  • Bottom-sheet Modal coverage — Project · Site/Zone · WBS with L1 tabs + horizontal scroll bar (v34.0)
📌
Reference file: ConstructQ_TaskMobile_v34.0.html · React SPA · phone frame · 4 roles · offline-first · Checklist Preview UI ported from Checklist Template v16.0 · NCR confirm dialog · rule chip + type-aware widgets

02Mobile Overview

Phone frame · 4 roles · 3 pickers · Checklist wizard

📱 Mobile Layout

  • Fixed viewport: Portrait 375×812 (iPhone 14) or 393×851 (Pixel 5) · phone frame on desktop
  • Status bar: Time · signal · battery · notch (iPhone)
  • Topbar: Back icon · title · language toggle · action buttons (role-dependent)
  • Content (scrollable): Task cards · list/detail · form fields
  • Bottom nav: 64px height · fixed at bottom · role-based icons
  • Bottom sheet modals: Project · Site/Zone · WBS · slide up · backdrop dim · constrained to phone frame (v32)

👥 4 Roles

RoleChipPrimary ActionsScreens Access
QCM · QC ManagerqcmCreate Task · assign inspector · approve NCR · publish workflowList · Create · Detail · Approve NCR
QC InspectorqiPerform Checklist inspection · Pass/Fail per item · create NCR on failList · Detail · Checklist wizard · Create NCR
Fixer / Sub ContractorfxReceive NCR · fix on-site · request re-verifyList (NCRs assigned) · NCR detail · request verify
Executive · CEOceoView-only · dashboard · overall progressList (read-only) · Detail (read-only)

➕ Create Task Form (QCM · v24.0 overhaul)

  • Project field — inline dropdown with project data (v29) · gradient banner + code chip
  • Site/Zone field — two-step bottom sheet Modal (v30-31)
  • WBS/Activity field — bottom sheet Modal with L1 tabs (Struct · Finishing · MEP) + visible horizontal scroll bar (v34)
  • Checklist field — link to template
  • Workflow field — link to workflow template
  • Inspector field — opens popup showing users in role (v24 rename)
  • Approver field — separate popup
  • Start Date + Duration · Ref Std · Risk (H/M/L)
  • Cancel + Save buttons — at bottom of form (v7 fixed)

✅ Checklist Wizard (QC Inspector · v18-23)

  • Tab strip per section (v21) · horizontal scroll on overflow
  • Circle stepper per section · numbered
  • Yellow ✓ badge on passed items (v20) · pinned across navigation
  • Rule chip · declarative rule statement (from Checklist v16.0)
  • Type-aware widgets · passfail · number+unit · radio · checkbox · text · longtext · photo · date · signature · color (v23)
  • Pass/Fail 2 large buttons · full-width
  • Wizard hides passed items (v15-16) · shows only uninspected
  • Completion screen · after all items · summary

🚨 NCR Create Flow

  • NCR Confirm dialog (v17) · on Fail tap · warns "จะสร้าง NCR"
  • Confirm button opens Create-NCR form (v6/v18 fix)
  • Cancel/Save buttons at bottom (v7)
  • Owner picker · web-style horizontal table (v9)
  • Sub Contractor auto-assigned based on WBS category

🎴 Card Layout (v12.0+ · matches web v28)

  • Header: Task code first, then status pill
  • Progress footer split · progress bar + %complete
  • Lifecycle pills in Thai (v14): สร้าง · มอบหมาย · ตรวจ · รอตรวจรับ · ปิด · NCR

03Mobile Test Strategy

Devices · gestures · offline · role-based flows

📱 Target Devices

DeviceViewportBrowserPriority
iPhone 14390×844Safari (WebKit)P1
Pixel 5393×851ChromeP1
iPhone SE375×667SafariP2 (small)
Samsung Galaxy S9+320×658ChromeP2 (narrow)

🎯 Test Types

  • Positive ~35% · Negative ~15% · Mobile ~20% · UI ~13%
  • Integration ~10% · Boundary ~5% · A11y ~2%

⚡ Priority

  • P1 — Critical · Create Task · Checklist wizard · NCR flow · touch input
  • P2 — High · Pickers · role gating · yellow ✓ badge · language
  • P3 — Medium · orientation · UI polish · edge devices

04Test Scenarios + Test Cases

16 areas · 82 test cases

4.1 · 🚀 App Launch + Role Switch

TC IDScenario StepsExpected Result PriorityType
TC-M01Launch as QCM · task list 1. Login as qcm role Phone frame · topbar QCM · task list · Create FAB visible P1Pos
TC-M02Launch as QC Inspector 1. Login as qi List filtered to assigned tasks · Create hidden P1Pos
TC-M03Launch as Fixer 1. Login as fx List filtered to NCRs assigned · workflow context P1Pos
TC-M04Launch as CEO · read-only 1. Login as ceo Read-only view · no Create · no edit actions P1Pos
TC-M05Topbar language toggle 1. Tap TH/EN flag Entire page switches language · task cards preserve TH data P1Pos

4.2 · 📋 Task List Card (v12+ · matches web v28)

TC IDScenario StepsExpected Result PriorityType
TC-M06Card layout · code + status + progress 1. View card Task code first · status pill · progress footer split (bar + %) P1UI
TC-M07Lifecycle pills · Thai (v14) 1. View status pill สร้าง · มอบหมาย · ตรวจ · รอตรวจรับ · ปิด · NCR · in Thai P2UI
TC-M08Empty state · no tasks 1. Filter to yield 0 results Empty message + illustration · role-specific text P2UI
TC-M09Card tap → detail view 1. Tap task card Full detail screen · back arrow · tabs (Overview / Checklist / NCR / Files) P1Pos
TC-M10Pull-to-refresh 1. Swipe down on list Refresh indicator · new tasks appear P2Mobile
TC-M11Infinite scroll · 20+ tasks 1. Scroll to bottom Load more · loading indicator · no hang P2Mobile
TC-M12Fixed bottom nav · 64px 1. View list Bottom nav stays at bottom · touch target ≥ 44px P1Mobile

4.3 · 🔍 Filter + Search

TC IDScenario StepsExpected Result PriorityType
TC-M13Filter tab · Search tab order (v11) 1. View filter bar Filter tab first · Search tab second (v11 swap) P3UI
TC-M14Search · filter by code 1. Type "TQ-2026-057" List filters to matching · results update on debounce P2Pos
TC-M15Filter by status · lifecycle chips 1. Tap สร้าง chip Only tasks with created status shown P2Pos
TC-M16Filter by project 1. Tap project filter Only tasks under selected project P2Pos

4.4 · ➕ QCM · Create Task (v24 overhaul)

TC IDScenario StepsExpected Result PriorityType
TC-M17Open Create form (QCM only) 1. QCM tap Create FAB Full form · fields · Cancel/Save at bottom (v7) P1Pos
TC-M18Non-QCM · Create hidden 1. Login as qi/fx/ceo Create FAB hidden or disabled P1Neg
TC-M19Project field · inline dropdown (v29) 1. Tap project field Bottom-sheet modal · project cards with gradient banner + code chip P1UI
TC-M20Required · Project + WBS + Checklist + Workflow 1. Skip fields · Save Error · block save · highlight missing P1Neg
TC-M21Start Date + Duration inputs 1. Set start · duration=7 Calculate due date = start + 7 days P2Pos
TC-M22Ref Std dropdown 1. Open Ref Std Grouped options (ACI · ASTM · วสท. · OSHA · etc.) P2Pos
TC-M23Risk (H/M/L) chips 1. Tap H chip Selected · H=red · M=amber · L=green P2UI
TC-M24Save valid task · success 1. All fields · Save Confirm popup · save · toast success · new card appears P1Pos
TC-M25Cancel · discard changes 1. Fill fields · Cancel Warning "จะทิ้งข้อมูล?" · discard → back to list P2Neg

4.5 · 🏗️ Project Picker (v25-29)

TC IDScenario StepsExpected Result PriorityType
TC-M26Open picker from Project field 1. Tap project field Bottom-sheet slides up · project list · grouped by status P1Mobile
TC-M27Project card · gradient + progress ring 1. View project card Color-per-project banner · code chip · circular progress ring · status badge · contract value · target date P1UI
TC-M28Filter · all/active/near/recent (v26) 1. Tap filter tab List filters by state P2Pos
TC-M29Select project · closes picker 1. Tap project card Picker closes · field shows selected project P1Pos
TC-M30Backdrop dismiss 1. Open picker · tap backdrop Picker closes · no selection P3Mobile

4.6 · 🗺️ Site/Zone Picker (v30-31 · 2-step bottom sheet)

TC IDScenario StepsExpected Result PriorityType
TC-M31Open picker · Step 1 sites 1. Tap Site/Zone field Bottom sheet · list of sites for selected project P1Mobile
TC-M32Step 2 · zones under site 1. Tap site → Step 2 Zone pills (v31) · tap to select P1Pos
TC-M33Back button · Step 2 → Step 1 1. Tap ◀ in Step 2 Return to sites list P2Mobile
TC-M34Confirm selection 1. Pick zone · Confirm Picker closes · field shows "Site A / Zone B" P1Pos

4.7 · 🌳 WBS Picker (v33-34 · L1 tabs + scroll bar)

TC IDScenario StepsExpected Result PriorityType
TC-M35Open picker · shows L1 tabs 1. Tap WBS field Bottom sheet · 3 L1 tabs (Struct · Finishing · MEP) P1UI
TC-M36L1 tab strip · horizontal scroll bar VISIBLE (v34) 1. View L1 tabs Scroll bar visible when overflow · not hidden P1UI
TC-M37Tap L1 · shows L2/L3 tree 1. Tap "Struct" Hierarchical L2 (Piling · Concrete) · L3 activities visible P1Pos
TC-M38Select L3 · confirms selection 1. Tap L3 "ตอกเสาเข็ม" Selected · Confirm → picker closes · field updated P1Pos
TC-M39Modal constrained to phone frame (v32) 1. Desktop preview · open picker Modal stays within phone frame · does NOT overflow to browser P1UI

4.8 · 👤 Owner Picker (v8-10)

TC IDScenario StepsExpected Result PriorityType
TC-M40Owner picker · web-style horizontal table (v9) 1. Tap Inspector field Popup · horizontal table · sorted by availability (v8) P1UI
TC-M41Owner picker · click handler works (v10 fix) 1. Tap owner row Selection registered · popup closes P1Pos
TC-M42Owner picker shows role members only 1. Inspector field → picker Only users with "QC Inspector" role listed P2Pos

4.9 · ✅ Checklist Wizard (QC Inspector · v18-23)

TC IDScenario StepsExpected Result PriorityType
TC-M43Enter wizard mode 1. QC Inspector · tap "เริ่มตรวจ Checklist" Wizard opens · tab strip + circle stepper visible P1Pos
TC-M44Screen renders (v22 fix) 1. Enter wizard Not blank · headerHtml renders · completion check works P1Neg
TC-M45Tab strip per section (v21) 1. Multi-section checklist Tabs at top · horizontal scroll on overflow P1UI
TC-M46Circle stepper · per section 1. View section 4 questions 4 circles numbered · current highlighted P1UI
TC-M47Rule chip · declarative statement (v23) 1. View question Chip shows rule text (matches Checklist Template v16.0) P2UI
TC-M48Type-aware widget · passfail 1. type=passfail question 2 large buttons: Pass (green) · Fail (red) · full width P1UI
TC-M49Type-aware widget · number+unit 1. type=number question Numeric keyboard opens · unit chip visible P1Mobile
TC-M50Wizard hides passed items (v15-16) 1. Pass q1 · continue Only uninspected items shown next · q1 hidden P2Pos
TC-M51Completion screen 1. Complete all items Summary screen · Pass/Fail count · Save button P1Pos
TC-M52Skip button REMOVED (v19) 1. Check passfail widget Only Pass/Fail · no Skip button P3UI

4.10 · 🟡 Yellow ✓ Badge on Stepper (v20)

TC IDScenario StepsExpected Result PriorityType
TC-M53Pass item · yellow ✓ badge shows 1. Pass q1 Circle 1 · yellow ✓ badge appears P2UI
TC-M54Pin ✓ across navigation 1. Pass q1 · go to q2 · back to q1 Yellow ✓ still on q1 · state pinned P2Pos
TC-M55Fail item · no yellow ✓ 1. Fail q1 Circle 1 · no ✓ · red styling P2UI
TC-M56Unanswered · plain circle 1. View unanswered question Plain circle · no badge P3UI

4.11 · 🚨 NCR Create Flow (v6/v17/v18)

TC IDScenario StepsExpected Result PriorityType
TC-M57Fail question · NCR confirm dialog opens (v17) 1. Tap Fail on question Dialog "จะสร้าง NCR ยืนยันไหม?" · Cancel/Confirm P1Pos
TC-M58Confirm opens Create-NCR form (v6 · v18 fix) 1. Tap Confirm on dialog Create-NCR form opens · pre-filled with task context P1Pos
TC-M59Cancel NCR dialog · returns to wizard 1. Tap Cancel Dialog closes · Fail not registered P2Neg
TC-M60NCR form · required fields 1. Skip title/desc · Save Error · block save P1Neg
TC-M61Cancel/Save buttons at bottom (v7) 1. View form footer Cancel + Save buttons fixed at bottom P2UI
TC-M62Save NCR · assigns Sub Contractor 1. Fill NCR · Save Task status → NCR · Sub Contractor auto-assigned · toast success P1Int

4.12 · 📄 Task Detail

TC IDScenario StepsExpected Result PriorityType
TC-M63Detail with tabs 1. Tap task card 4 tabs: Overview · Checklist · NCR · Files P1Pos
TC-M64Overview tab · task fields 1. Tap Overview Code · status · dates · WBS · owner · risk P1Pos
TC-M65Checklist tab · start inspection 1. Tap Checklist Preview + "เริ่มตรวจ" button (v4 · expand/collapse details) P1Pos
TC-M66NCR tab · list NCRs 1. Tap NCR Show related NCRs · view button per row P2Pos

4.13 · 🌐 Language TH/EN

TC IDScenario StepsExpected Result PriorityType
TC-M67Toggle TH → EN (v5 · smaller lang toggle) 1. Tap EN flag Full UI switches · TH task data preserved P1Pos
TC-M68Preserve data on toggle 1. Fill form · toggle · back Data intact · no reset P1Pos

4.14 · 📡 Offline / Poor Network

TC IDScenario StepsExpected Result PriorityType
TC-M69Offline · checklist writes queue locally 1. Airplane mode · complete checklist Answers saved locally · sync on reconnect P1Mobile
TC-M70Slow 3G · loading state 1. Throttle 3G · save task Spinner · disable button · no double-submit P2Mobile
TC-M71Photo upload · resume on reconnect 1. Take photo offline · reconnect Photo uploads · progress indicator · retry on failure P2Int

4.15 · 🔄 Safe Area + Orientation

TC IDScenario StepsExpected Result PriorityType
TC-M72Portrait only 1. Rotate landscape Stay portrait or show hint · UI works P3UI
TC-M73Safe area · notch/home indicator 1. iPhone 14 with notch Content respects safe-area-inset · no overlap P2Mobile
TC-M74Modal constrained to phone frame (v32) 1. Desktop · open any bottom sheet Modal within frame · not full-window P1UI

4.16 · ♿ Mobile Accessibility

TC IDScenario StepsExpected Result PriorityType
TC-M75Touch target · min 44×44 px 1. Audit buttons All ≥ 44px · Material 48dp P2Mobile
TC-M76Tap ripple/feedback 1. Tap Pass/Fail button Visual feedback · immediate response P2Mobile
TC-M77VoiceOver · button labels 1. Enable VoiceOver · swipe Buttons announced · Pass · Fail · Camera P2A11y
TC-M78TalkBack · picker announces state 1. TalkBack · open picker "Bottom sheet · opened · project list" P2A11y
TC-M79Contrast · WCAG AA 1. Run axe scan Text contrast ≥ 4.5:1 P2A11y

4.17 · 🎯 Bottom Nav + FAB

TC IDScenario StepsExpected Result PriorityType
TC-M80Bottom nav tabs · role-specific 1. QCM vs QI vs Fixer Nav items differ by role · badge counts (NCR waiting) P2Pos
TC-M81FAB · Create button (QCM only) 1. QCM · FAB visible bottom right Circular button · shadow · above bottom nav P2UI
TC-M82FAB hidden for non-creators 1. Inspector view No FAB · no Create option P2Neg
📊
Test Case Summary: รวม 82 mobile test cases · P1 = 38 · P2 = 34 · P3 = 10 · Positive 27 · Negative 8 · Boundary 0 · Integration 3 · Mobile 15 · UI 24 · A11y 5

05Playwright Mobile Automation Guide + 5 Sample Scripts

Setup · POM · bottom sheet · checklist wizard · role emulation

🚀 Setup

  1. Install: npm init playwright@latest + Mobile Chrome + WebKit projects
  2. Devices: Configure Pixel 5 + iPhone 14 in playwright.config.ts
  3. Touch enabled: hasTouch: true · isMobile: true
  4. Role fixture: Set cookie/localStorage for role (qcm/qi/fx/ceo)
  5. Seed data: Pre-populate tasks/projects/checklists · reset before each test

📁 Project Structure

treetests/task-mobile/
├── launch.spec.ts              # TC-M01 to TC-M05
├── task-list.spec.ts           # TC-M06 to TC-M12
├── filter-search.spec.ts       # TC-M13 to TC-M16
├── create-task.spec.ts         # TC-M17 to TC-M25 (QCM)
├── project-picker.spec.ts      # TC-M26 to TC-M30
├── site-zone-picker.spec.ts    # TC-M31 to TC-M34
├── wbs-picker.spec.ts          # TC-M35 to TC-M39 (v34 scroll bar)
├── owner-picker.spec.ts        # TC-M40 to TC-M42
├── checklist-wizard.spec.ts    # TC-M43 to TC-M52 (QC Inspector)
├── yellow-badge.spec.ts        # TC-M53 to TC-M56 (v20)
├── ncr-create.spec.ts          # TC-M57 to TC-M62 (Fail flow)
├── task-detail.spec.ts         # TC-M63 to TC-M66
├── language.spec.ts            # TC-M67, TC-M68
├── offline.spec.ts             # TC-M69 to TC-M71
├── orientation.spec.ts         # TC-M72 to TC-M74
├── a11y.spec.ts                # TC-M75 to TC-M79
├── bottom-nav.spec.ts          # TC-M80 to TC-M82
└── e2e-full-flow.spec.ts       # Composite QCM → QI → NCR
pages/
├── TaskListPage.ts
├── CreateTaskPage.ts
├── ProjectPicker.ts
├── SiteZonePicker.ts
├── WBSPicker.ts
├── ChecklistWizard.ts
└── NCRCreateForm.ts
playwright.config.ts

⚙️ playwright.config.ts

TypeScriptimport { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/task-mobile',
  fullyParallel: true,
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    locale: 'th-TH',
  },
  projects: [
    { name: 'iPhone 14', use: { ...devices['iPhone 14'] }},
    { name: 'Pixel 5',   use: { ...devices['Pixel 5'] }},
    { name: 'iPhone SE', use: { ...devices['iPhone SE'] }},
    { name: 'Galaxy S9+',use: { ...devices['Galaxy S9+'] }},
  ]
});

🎯 POM · CreateTaskPage

TypeScriptimport { Page, Locator, expect } from '@playwright/test';

export type Role = 'qcm' | 'qi' | 'fx' | 'ceo';
export type Risk = 'H' | 'M' | 'L';

export class CreateTaskPage {
  readonly page: Page;
  readonly fabCreate: Locator;
  readonly projectField: Locator;
  readonly siteZoneField: Locator;
  readonly wbsField: Locator;
  readonly checklistField: Locator;
  readonly workflowField: Locator;
  readonly inspectorField: Locator;
  readonly btnSave: Locator;
  readonly btnCancel: Locator;

  constructor(page: Page) {
    this.page = page;
    this.fabCreate = page.locator('.tm-fab-create');
    this.projectField = page.locator('[data-field="project"]');
    this.siteZoneField = page.locator('[data-field="site-zone"]');
    this.wbsField = page.locator('[data-field="wbs"]');
    this.checklistField = page.locator('[data-field="checklist"]');
    this.workflowField = page.locator('[data-field="workflow"]');
    this.inspectorField = page.locator('[data-field="inspector"]');
    this.btnSave = page.getByRole('button', { name: /บันทึก|Save/ });
    this.btnCancel = page.getByRole('button', { name: /ยกเลิก|Cancel/ });
  }

  async openCreateForm() {
    await this.fabCreate.tap();
  }

  // Project picker (bottom sheet)
  async pickProject(projectCode: string) {
    await this.projectField.tap();
    const sheet = this.page.locator('.pk-project-sheet');
    await sheet.waitFor({ state: 'visible' });
    await sheet.locator(`[data-project-code="${projectCode}"]`).tap();
  }

  // Site/Zone 2-step picker
  async pickSiteZone(site: string, zone: string) {
    await this.siteZoneField.tap();
    const sheet = this.page.locator('.pk-modal-sites');
    await sheet.waitFor({ state: 'visible' });
    await sheet.getByText(site).tap();  // Step 1
    await sheet.locator('.pk-zone-pill').getByText(zone).tap();  // Step 2
    await sheet.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap();
  }

  // WBS picker · L1 tabs + L3 (v34 scroll bar)
  async pickWBS(l1Category: 'Struct' | 'Finishing' | 'MEP', l3ActivityName: string) {
    await this.wbsField.tap();
    const sheet = this.page.locator('.pk-wbsm');
    await sheet.waitFor({ state: 'visible' });
    await sheet.locator(`.pk-wbsm-l1s [data-l1="${l1Category}"]`).tap();
    await sheet.locator('.pk-wbsm-l3').getByText(l3ActivityName).tap();
    await sheet.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap();
  }

  async pickInspector(inspectorName: string) {
    await this.inspectorField.tap();
    const popup = this.page.locator('.pk-owner-popup');
    await popup.waitFor({ state: 'visible' });
    await popup.getByText(inspectorName).tap();
  }

  async setRisk(risk: Risk) {
    await this.page.locator(`.tm-risk-chip[data-risk="${risk}"]`).tap();
  }

  async save() {
    await this.btnSave.tap();
    await this.page.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap();
  }
}

✅ Sample Test 1 · Role-Based Access (QCM vs QI vs CEO)

TypeScriptimport { test, expect } from '@playwright/test';

test.describe('Task Mobile · Role-based access', () => {
  test('TC-M01 · QCM · FAB visible + Create form works', async ({ page, context }) => {
    await context.addCookies([{ name: 'role', value: 'qcm', url: 'http://localhost:3000' }]);
    await page.goto('/task-mobile');
    await expect(page.locator('.tm-fab-create')).toBeVisible();
  });

  test('TC-M18 · QC Inspector · FAB hidden', async ({ page, context }) => {
    await context.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]);
    await page.goto('/task-mobile');
    await expect(page.locator('.tm-fab-create')).toBeHidden();
  });

  test('TC-M04 · CEO · read-only view', async ({ page, context }) => {
    await context.addCookies([{ name: 'role', value: 'ceo', url: 'http://localhost:3000' }]);
    await page.goto('/task-mobile');
    await expect(page.locator('.tm-fab-create')).toBeHidden();
    await expect(page.locator('.tm-edit-btn')).toBeHidden();
  });
});

✅ Sample Test 2 · WBS Picker (v34 scroll bar)

TypeScripttest('TC-M36 · WBS Modal L1 tabs · horizontal scroll bar visible (v34)', async ({ page }) => {
  const create = new CreateTaskPage(page);
  await page.goto('/task-mobile');
  await create.openCreateForm();
  await create.pickProject('PJ-001');
  await create.wbsField.tap();

  const tabs = page.locator('.pk-wbsm-l1s');
  await expect(tabs).toBeVisible();

  // Verify overflow-x auto/scroll · v34 requirement
  const overflowX = await tabs.evaluate((el) => window.getComputedStyle(el).overflowX);
  expect(['auto', 'scroll']).toContain(overflowX);

  // Verify tabs overflow the container width
  const scrollWidth = await tabs.evaluate((el) => el.scrollWidth);
  const clientWidth = await tabs.evaluate((el) => el.clientWidth);
  expect(scrollWidth).toBeGreaterThanOrEqual(clientWidth);

  // Verify all 3 L1 tabs present
  for (const cat of ['Struct', 'Finishing', 'MEP'] as const) {
    await expect(tabs.locator(`[data-l1="${cat}"]`)).toBeVisible();
  }

  // Verify modal constrained to phone frame (v32)
  const box = await page.locator('.pk-wbsm').boundingBox();
  const viewport = page.viewportSize()!;
  expect(box!.width).toBeLessThanOrEqual(viewport.width);
});

✅ Sample Test 3 · Checklist Wizard with Yellow ✓ Badge (v20)

TypeScripttest('TC-M53 + TC-M54 · Pass item shows yellow ✓ pinned across nav', async ({ page, context }) => {
  await context.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]);
  await page.goto('/task-mobile');
  await page.locator('.tm-card').first().tap();
  await page.getByRole('tab', { name: /Checklist/ }).tap();
  await page.getByRole('button', { name: /เริ่มตรวจ Checklist/ }).tap();

  // Wizard opens · press Pass on q1 (assumes passfail type)
  const passBtn = page.locator('.ck-pf-pass').first();
  await passBtn.tap();

  // Verify yellow ✓ badge appears on stepper circle 1
  const circle1 = page.locator('.ck-stepper-circle').first();
  const checkIcon = circle1.locator('.ck-stepper-check');
  await expect(checkIcon).toBeVisible();

  // Verify color is yellow (#FFD23F or equivalent)
  const color = await checkIcon.evaluate((el) => window.getComputedStyle(el).color);
  expect(color).toMatch(/rgb\(255,\s*210,\s*63\)|#FFD23F/i);

  // Navigate away (to q2) and back to q1 · verify ✓ still pinned (v14/v20)
  await page.locator('.ck-stepper-circle').nth(1).tap();
  await page.locator('.ck-stepper-circle').first().tap();
  await expect(checkIcon).toBeVisible();  // still there
});

✅ Sample Test 4 · NCR Create Flow (Fail → Confirm → Form)

TypeScripttest('TC-M57 + TC-M58 · Fail opens NCR confirm · Confirm opens Create-NCR form', async ({ page, context }) => {
  await context.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]);
  await page.goto('/task-mobile');
  await page.locator('.tm-card').first().tap();
  await page.getByRole('button', { name: /เริ่มตรวจ Checklist/ }).tap();

  // Tap Fail on first passfail question
  await page.locator('.ck-pf-fail').first().tap();

  // NCR Confirm dialog appears (v17)
  const dialog = page.getByRole('dialog');
  await expect(dialog).toContainText(/จะสร้าง NCR|Create NCR/);

  // Confirm → opens Create-NCR form (v6 · v18 fix)
  await dialog.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap();

  const ncrForm = page.locator('.ncr-create-form');
  await expect(ncrForm).toBeVisible();

  // Verify Cancel + Save buttons at bottom (v7)
  await expect(page.locator('.ncr-create-form-footer button')).toHaveCount(2);

  // Fill NCR form
  await page.getByLabel(/หัวข้อ NCR|NCR Title/).fill('พบรอยแตกที่คอนกรีต');
  await page.getByLabel(/รายละเอียด|Description/).fill('รอยแตก 3 มม. ที่ตำแหน่ง P-2');

  // Save NCR
  await page.getByRole('button', { name: /บันทึก|Save/ }).tap();
  await expect(page.getByText(/สร้าง NCR เรียบร้อย|NCR created/)).toBeVisible();
});

✅ Sample Test 5 · Full E2E · QCM Creates Task → Inspector Inspects → NCR Loop

TypeScripttest('TC-E2E-01 · Full lifecycle · QCM creates → QI inspects → NCR created', async ({ browser }) => {
  // === Step 1: QCM creates task ===
  const qcmContext = await browser.newContext({ ...devices['Pixel 5'] });
  await qcmContext.addCookies([{ name: 'role', value: 'qcm', url: 'http://localhost:3000' }]);
  const qcmPage = await qcmContext.newPage();
  const create = new CreateTaskPage(qcmPage);

  await qcmPage.goto('/task-mobile');
  await create.openCreateForm();
  await create.pickProject('PJ-001');
  await create.pickSiteZone('อาคาร A', 'Zone 1');
  await create.pickWBS('Struct', 'ตอกเสาเข็ม');
  await create.checklistField.tap();
  await qcmPage.getByText('ตรวจงานคอนกรีต').tap();
  await create.workflowField.tap();
  await qcmPage.getByText('Standard + NCR Loop').tap();
  await create.pickInspector('สมชาย ใจดี');
  await create.setRisk('H');
  await create.save();
  await expect(qcmPage.getByText(/สร้างเรียบร้อย/)).toBeVisible();

  const taskCode = await qcmPage.locator('.tm-card').first()
    .locator('.tm-code').textContent();
  await qcmContext.close();

  // === Step 2: QC Inspector inspects · fails · triggers NCR ===
  const qiContext = await browser.newContext({ ...devices['Pixel 5'] });
  await qiContext.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]);
  const qiPage = await qiContext.newPage();
  await qiPage.goto('/task-mobile');
  await qiPage.getByText(taskCode!).tap();
  await qiPage.getByRole('button', { name: /เริ่มตรวจ Checklist/ }).tap();

  // Fail first question · trigger NCR
  await qiPage.locator('.ck-pf-fail').first().tap();
  await qiPage.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap();

  // Fill NCR
  await qiPage.getByLabel(/หัวข้อ NCR/).fill('พบรอยแตก');
  await qiPage.getByRole('button', { name: /บันทึก|Save/ }).tap();

  // Verify NCR created + Sub Contractor auto-assigned
  await expect(qiPage.getByText(/NCR created|สร้าง NCR/)).toBeVisible();
  await qiContext.close();
});
💡
Best Practices: Use tap() not click() · use data-role, data-field, data-project-code attributes for reliable selectors · set role cookie before each test · use POM for each picker (Project/Site/WBS/Owner) · verify modal within phone frame via boundingBox width comparison

06Appendix

📌 Version History

  • ConstructQ_TaskMobile_v34.0.html · source · WBS modal L1 tabs · visible horizontal scrollbar
  • เอกสารนี้ v1.0 · 13 ก.ค. 2569 · 82 test cases · 5 sample Playwright scripts

🔗 References

  • Playwright Mobile: https://playwright.dev/docs/emulation
  • Related file: web ConstructQ_TaskManagement(QC+NCR)_v40.0.html · shares data structures
  • Checklist port: ConstructQ_Checklist_v16.0.html (rule chip + type-aware widgets v23)
  • Excel companion: ConstructQ_TaskMobile_TestScenario_v1.0.xlsx